/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.gwt.widgets.client;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.user.client.ui.CellPanel;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;

/**
 * GWT makes it easy to create and listen to the widgets that the user
 *  interacts with (like buttons, textboxes, etc.) but when designing a UI
 *  one can spend a considerable amount of time in layout of these interactive
 *  widgets - positioning and spacing them just right. GWT does provide many 
 *  layout Panels but their usage can create verbose code that is hard 
 *  to modify or grasp after the layout is written.   
 *  <br><br>
 *  AxisPanel provides a simple concise way to layout a complex UI using short
 *  continous expressions that place your widgets in either the X or Y axis 
 *  allowing you to recursively create a layout of any degree of complexity.
 *  Every intermediate X or Y axis panel is stylable and customizable 
 *  on the fly within the same expression. AxisPanel can shrink a page length 
 *  of Java code to one easy-to-read expression which enables easy 
 *  experimentation until your layout is achieved. Allows you to focus your 
 *  time/energy on the interesting dynamics that GWT handles so beautifully 
 *  and  less on the not-so-interesting layout details.  AxisPanel is also more 
 *  concise than its equivalent HTML or Declarative XML type layouts.  
 *  <br><br>
 * <b>Sample Usage:</b>
 *  <code><pre>
 *  AxisPanel p = new AxisPanel();      
 *  p.id("search").style("width","800px").style("padding","10px").align(0,0).border(1)
 *      .y().idExt("options").style("width", "80%").style("padding", "5px").align(0,0).border(1)
 *          .x("Enter your Search here:").q()
 *          .x(searchBox,searchButton).style("width","80%").style("padding","5px").q()
 *          .q()
 *      .y().idExt("results").style("width","80%").style("padding","5px").border(1)
 *          .x().css("header").style("width","80%").style("padding","5px").align(0,0).border(1)
 *             .x(textbox2, label2).q()
 *             .y(textbox3, button3).q()
 *             .q()
 *          .x().css("help").border(1).style("width","80%").style("padding","5px")
 *              .x(helpLabel, helpSearchBox, helpHyperLink).q()
 *              .y(textbox5, button5).q()
 *              .q()
 *          .x(searchResultsTable).css("results").style("width","80%").style("padding","5px").q()              
 *          .y(textbox6,button6).q()
 *          .q()
 *  </code></pre>
 *  <b>How to Read:</b><br>
 *  <ul>
 *  <li>
 *  AxisPanel<br>
 *      The root panel and the starting target to which all additions go.
 *      The AxisPanel lays out in Y Axis to start with (vertical layout).
 *  </li>
 *  <li>
 *  .y(Widget...)<br>
 *      Adds a vertical or Y Axis to current target panel 
 *      with the given Widgets. Switches target to this new Y Axis Panel and all 
 *      further actions are directed to this Panel.  The added widgets will 
 *      line up vertically (Y Axis) within this new Panel.
 *   </li>
 *   <li>
 *  .x(Widget...)<br> 
 *      Adds a Horizontal or X Axis panel to current target panel 
 *      with the given Widgets. Switches target to this new X Axis Panel and all 
 *      further actions are directed to this Panel.  The added widgets will 
 *      line up horizontally (X Axis) within this new Panel.
 *      .x(String) is convenience shortcut for .x(new Label(String)).
 *  </li>
 *  <li>
 *  .q()<br> 
 *      Quit adding to the current target Panel.
 *      Switches target back to parent panel in use before this panel.
 *      Every x() or y() axis addtions should end with a q().  
 *  </li>
 *  <li>
 *  indentation<br>
 *      All children are indented.
 *  </li>
 *  </ul>
 *  
 *  <b>CSS classes provided</b><br>
 *  AxisPanel, AxisPanelX, AxisPanelY, AxisPanelWidgetWrapper (axis tree) <br>
 *  AxisPanelLayout, AxisPanelXLayout, AxisPanelYLayout (layout tree)
 * <br>
 * <br>
 *  
 * @author Sony Mathew
 */
public class AxisPanel2 extends AxisWrapperPanel  {

    private AxisPanel2 target = this; 
    
    /**
     * Identify the Axis as X or Y (Horizontal or Vertical).
     */
    public enum Axis { X, Y }
    
    /**
     * Represents the X or HorizontalPanel.
     */
    static class XLayoutPanel extends HorizontalPanel {        
    }
    
    /**
     * Represents the Y or VerticalPanel.
     */
    static class YLayoutPanel extends VerticalPanel {
    }
    
    private final Axis axis;
    private final CellPanel layoutPanel;
    private String id = null;
    
    private Map<String, String> axisChildsStyles = null;
    private Map<String, String> axisDescendsStyles = null;       
    
    private List<String> axisChildsCssClasses = null;
    private List<String> axisDescendsCssClasses = null;

    private Map<String, String> layoutChildsStyles = null;
    private Map<String, String> layoutDescendsStyles = null;
    
    private List<String> layoutChildsCssClasses = null;
    private List<String> layoutDescendsCssClasses = null;
    
    /**
     * By default we are a Y axis panel.
     * 
     * author Sony Mathew
     */
    public AxisPanel2() {
        this(Axis.Y);
    }
    
    /**
     * author Sony Mathew
     */
    public AxisPanel2(Axis axis) {
        this.axis = axis;
        
        addStyleName("AxisPanel");
        
        if (this.axis.equals(Axis.X)) {
            addStyleName("AxisPanelX");
            layoutPanel = new XLayoutPanel();
            layoutPanel.addStyleName("AxisPanelXLayout");
        }else{
            addStyleName("AxisPanelY");
            layoutPanel = new YLayoutPanel();            
            layoutPanel.addStyleName("AxisPanelYLayout");
        }
        
        layoutPanel.addStyleName("AxisPanelLayout");
        setWidget(layoutPanel);
    }

    /**
     * Checks if the target is still valid.
     * author Sony Mathew
     */
    private void checkTarget() {
        if (target == null) {
            throw new IllegalStateException("AxisPanel is closed for further building");
        }
    }
    
    /**
     * Obtain the current target of operations.
     * 
     * author Sony Mathew
     */
    private AxisPanel2 getTarget() {
        checkTarget();
        return target;
    }

    /**
     * Obtain the current target of operations.
     * 
     * author Sony Mathew
     */
    private void setTarget(AxisPanel2 newTarget) {
        checkTarget();
        target = newTarget;
    }
    
    /**
     * Adds a Vertical Panel to the current target, this new panel 
     *    will be the new target for all actions until next q().
     *      
     * Also provide 1 or more widgets for immediate addition.
     * 
     * @param ws: The widgets being added to the new Y Axis Panel.
     * @return AxisPanel: Self
     */
    public AxisPanel2 y(Widget...ws) {
        addTarget(new AxisPanel2(Axis.Y));
        addWidgets(ws);
        return this;
    }
        
    /**
     * Convenience for Collection of widgets.
     * 
     * @see #y(Widget[])
     */
    public AxisPanel2 y(Collection<? extends Widget> ws) {
        if (ws==null) {
            ws = Collections.emptyList(); 
        }
        return y(ws.toArray(new Widget[ws.size()]));
    }
        
    /**
     * Adds a HorizontalPanel to the current target and all new Widgets added 
     *      will now go into this Panel until a q() call. 
     *      
     * @param ws: The widgets being added to the new Y Axis Panel.
     * @return AxisPanel: Self
     */    
    public AxisPanel2 x(Widget...ws) {
        addTarget(new AxisPanel2(Axis.X));
        addWidgets(ws);        
        return this;
    }
       
    /**
     * Convenience for Collection of widgets.
     * 
     * @see #x(Widget[])
     */
    public AxisPanel2 x(Collection<? extends Widget> ws) {
        if (ws==null) {
            ws = Collections.emptyList(); 
        }
        return x(ws.toArray(new Widget[ws.size()]));
    }
    
    /**
     * Convenience method. Same as x(new Label(s)).
     */
    public AxisPanel2 x(String s) {
        return x(new Label(s));
    }
    
    /**
     * Provide a Panel that decorates the current getTarget().
     *      The current target will be added to the given decorating panel.
     *      using its add() method.
     * 
     * @param decorPanel: The Panel to decorate the current target with.
     * @return AxisPanel: Self
     */
    public AxisPanel2 decor(HasWidgets decorPanel) {
        HasWidgets targetParent = (HasWidgets)getTarget().getParent();
        decorPanel.add(getTarget());
        if (targetParent != null) {
            targetParent.add((Widget)decorPanel);
        }
        return this;        
    }
        
    /**
     * Indicate that the current panel being built is done.
     *  All further actions will target the parent of the current target panel.
     *  
     * @return AxisPanel: Self
     */
    public AxisPanel2 q() {
        applyChildStyles(getTarget());
        clearChildStyles(getTarget()); //no longer needed, target has been built and all styles applied.
        AxisPanel2 parent = getTargetableParentOf(getTarget());
        setTarget(parent);
        return this;
    }
        
    /**
     * Adds Widgets to the current target X | Y panel.
     * Can put null widget which is merely ignored.
     *  This is for convenience.
     *      
     * @param ws: The widgets being added.
     * @return AxisPanel: Self
     */    
    public AxisPanel2 put(Widget...ws) {
        addWidgets(ws);        
        return this;
    }
       
    /**
     * Convenience for Collection of widgets.
     * 
     * @see #x(Widget[])
     */
    public AxisPanel2 put(Collection<? extends Widget> ws) {
        if (ws==null) {
            ws = Collections.emptyList(); 
        }
        return put(ws.toArray(new Widget[ws.size()]));
    }
    
    /**
     * Convenience for put(new Label(String));
     */    
    public AxisPanel2 put(String label) {               
        return put(new Label(label));
    }
    
    /**
     * Adds the given widget to the current target and makes this widget the 
     *  new target for all further actions.
     */
    private void addTarget(AxisPanel2 p) {
        getTarget().layoutPanel.add(p);
        setTarget(p);
    }
        
    /**
     * Add widgets to the current target panel being built.
     */
    private void addWidgets(Widget...ws) {
        if (ws != null && ws.length != 0) {            
            for(Widget w: ws) {
                if (w != null) {
                    AxisWrapperPanel wrapper = new AxisWrapperPanel();
                    wrapper.setWidget(w);
                    wrapper.addStyleName("AxisPanelWidgetWrapper");
                    getTarget().layoutPanel.add(wrapper);                    
                }
            }            
        }
    }
        
    /**
     * Obtain the first targetable parent of the current getTarget().
     * 
     * @return : The parent - can be null if no parent.
     * 
     * author Sony Mathew
     */
    private AxisPanel2 getTargetableParentOf(Widget child) {
        Widget parent = child.getParent();
        while(parent != null && !(parent instanceof AxisPanel2)) {
            parent = parent.getParent();
        }        
        return (AxisPanel2)parent;
    }

    /**
     * Set the id of the target panel.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 id(String anId) {
        getTarget().id = anId;
        getTarget().getElement().setId(getTarget().id);
        return this;
    }
    
    /**
     * Extend the id of parent panel to create id of the target panel.
     * 
     * The id of this panel is this idExt prefixed with the id of its parent AxisPanel2 separated by a '-'.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 idExt(String idExt) {
        String parentId = getTargetableParentIdOf(getTarget());
        getTarget().id = ((parentId != null)? (parentId+"-") : "") + idExt; 
        getTarget().getElement().setId(getTarget().id);
        return this;        
    }

    /**
     * Obtain the id of the first parent in the ancestory (of AxisPanels only)
     *  that has a non-null id.
     * 
     * @param : id of first AxisPanel parent in ancestory.  Can return null if no id exists.
     * 
     * author Sony Mathew
     */
    private String getTargetableParentIdOf(AxisPanel2 p) {
        AxisPanel2 parent = getTargetableParentOf(p);
        if (parent != null) {
            if (parent.id != null) {
                return parent.id;
            }else{
                return getTargetableParentIdOf(parent);
            }
        }else{
            return null;
        }
    }
    
    /**
     * Style the target panel with the given property and value.
     *  Note: This styles only the target and not its inner layout panel.
     *      Can use styleLayout() to style the target's layout panel directly. 
     *  
     * author Sony Mathew
     */
    public AxisPanel2 style(String styleProp, String styleValue) {
        getTarget().getElement().getStyle().setProperty(styleProp, styleValue);
        return this;
    }
    
    /**
     * Style all children of the target panel to the given property and value.
     * 
     * Will style only AxisWrapperPanels i.e. X and Y panels.
     *  Additionally widgets added to AxisPanel are all wrapped in AxisWrapperPanels.
     *      and will also be styled using this method.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 styleChilds(String styleProp, String styleValue) {
        if (getTarget().axisChildsStyles == null) {            
            getTarget().axisChildsStyles = new LinkedHashMap<String, String>();
        }          
        
        getTarget().axisChildsStyles.put(styleProp, styleValue);
        return this;
    }

    /**
     * Style all the descendents (children, grand-children and so on) of the target panel 
     *      to the given property and value.  Until the target panel is done building using q().
     *      
     * Will style only AxisWrapperPanels i.e. X and Y panels.
     *  Additionally widgets added to AxisPanel are all wrapped in AxisWrapperPanels.
     *      and will also be styled using this method.
     *  
     * author Sony Mathew
     */
    public AxisPanel2 styleDescends(String styleProp, String styleValue) {
        if (getTarget().axisDescendsStyles == null) {            
            getTarget().axisDescendsStyles = new LinkedHashMap<String, String>();
        }                
        
        getTarget().axisDescendsStyles.put(styleProp, styleValue);
        return this;
    }
    
    /**
     * Style the layout of the target panel with the given property and value.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 styleLayout(String styleProp, String styleValue) {
        getTarget().layoutPanel.getElement().getStyle().setProperty(styleProp, styleValue);
        return this;
    }

    /**
     * Style all layout children of the target panel to the given property and value.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 styleLayoutChilds(String styleProp, String styleValue) {
        if (getTarget().layoutChildsStyles == null) {            
            getTarget().layoutChildsStyles = new LinkedHashMap<String, String>();
        }          
        
        getTarget().layoutChildsStyles.put(styleProp, styleValue);
        return this;
    }

    /**
     * Style all the layout descendents (children, grand-children and so on) of the target panel 
     *      to the given property and value.
     *      
     * author Sony Mathew
     */
    public AxisPanel2 styleLayoutDescends(String styleProp, String styleValue) {
        if (getTarget().layoutDescendsStyles == null) {            
            getTarget().layoutDescendsStyles = new LinkedHashMap<String, String>();
        }                
        
        getTarget().layoutDescendsStyles.put(styleProp, styleValue);
        return this;
    }
    
    /**
     * Add a CSS class name to the target panel.
     * 
     * @see #style(String, String) 
     */
    public AxisPanel2 css(String cssClassName) {
        getTarget().addStyleName(cssClassName);
        return this;
    }    

    /**
     * Add a CSS class name to the target panel's children.
     * 
     * @see #style(String, String) 
     */
    public AxisPanel2 cssChilds(String cssClassName) {
        if (getTarget().axisChildsCssClasses == null) {
            getTarget().axisChildsCssClasses = new ArrayList<String>();
        }
        getTarget().axisChildsCssClasses.add(cssClassName);
        return this;
    }    

    /**
     * Add a CSS class name to the target panel's descendents (children included).
     * 
     * @see #style(String, String) 
     */
    public AxisPanel2 cssDescends(String cssClassName) {
        if (getTarget().axisDescendsCssClasses == null) {
            getTarget().axisDescendsCssClasses = new ArrayList<String>();
        }
        getTarget().axisDescendsCssClasses.add(cssClassName);
        return this;
    }    

    /**
     * Add a CSS class name to the target panel's layout.
     * 
     * @see #style(String, String) 
     */
    public AxisPanel2 cssLayout(String cssClassName) {
        getTarget().layoutPanel.addStyleName(cssClassName);
        return this;
    }    

    /**
     * Add a CSS class name to the target panel's layout children.
     * 
     * @see #style(String, String) 
     */
    public AxisPanel2 cssLayoutChilds(String cssClassName) {
        if (getTarget().layoutChildsCssClasses == null) {
            getTarget().layoutChildsCssClasses = new ArrayList<String>();
        }
        getTarget().layoutChildsCssClasses.add(cssClassName);
        return this;
    }    

    /**
     * Add a CSS class name to the target panel's layout descendents (children included).
     * 
     * @see #style(String, String) 
     */
    public AxisPanel2 cssLayoutDescends(String cssClassName) {
        if (getTarget().layoutDescendsCssClasses == null) {
            getTarget().layoutDescendsCssClasses = new ArrayList<String>();
        }
        getTarget().layoutDescendsCssClasses.add(cssClassName);
        return this;
    }    
    
    /**
     * Applies all the child & descendent styles and css classes recorded by the parent and ancestors of the given child. 
     * 
     * author Sony Mathew
     */
    private void applyChildStyles(AxisWrapperPanel child) {
        if (child == null) {
            return;
        }
        
        AxisPanel2 parent = getTargetableParentOf(child);
        
        if (parent == null) {
            return;
        }
        
        //apply child styles recorded on parent.
        if (parent.axisChildsStyles != null) {
            for(Map.Entry<String,String> childStyle: parent.axisChildsStyles.entrySet()) {            
                child.getElement().getStyle().setProperty(childStyle.getKey(), childStyle.getValue());
            }
        }

        //apply child css classes recorded on parent.
        if (parent.axisChildsCssClasses != null) {
            for(String childCssClass: parent.axisChildsCssClasses) {            
                child.addStyleName(childCssClass);
            }
        }
        
        if (child instanceof AxisPanel2) {
            AxisPanel2 childAxisPanel = (AxisPanel2)child;
            
            //apply layout child styles recorded on parent.            
            if (parent.layoutChildsStyles != null) {                
                for(Map.Entry<String,String> layoutChildStyle: parent.layoutChildsStyles.entrySet()) {            
                    childAxisPanel.layoutPanel.getElement().getStyle().setProperty(layoutChildStyle.getKey(), layoutChildStyle.getValue());
                }
            }
            
            //apply layout child classes recorded on parent.            
            if (parent.layoutChildsCssClasses != null) {
                for(String layoutChildCssClass: parent.layoutChildsCssClasses) {            
                    childAxisPanel.layoutPanel.addStyleName(layoutChildCssClass);
                }
            }  
            
            Iterator<Widget> grandChildIter = childAxisPanel.layoutPanel.iterator();
            while (grandChildIter.hasNext()) {
                Widget grandChild = grandChildIter.next();
                if (grandChild instanceof AxisWrapperPanel) {
                    applyChildStyles((AxisWrapperPanel)grandChild); 
                }
            }
        }
        
        applyDescendsStyles(child, parent);
    }
    
    /**
     * Applies all the child & descendent styles and css classes recorded by the parent and ancestors of the given child.
     *  Starting with the parent specified. 
     * 
     * author Sony Mathew
     */
    private void applyDescendsStyles(AxisWrapperPanel child, AxisPanel2 parent) {     
        if (child == null || parent == null) {
            return;
        }
        
        //apply descend styles recorded on parent.
        if (parent.axisDescendsStyles != null) {
            for(Map.Entry<String,String> descendStyle: parent.axisDescendsStyles.entrySet()) {            
                child.getElement().getStyle().setProperty(descendStyle.getKey(), descendStyle.getValue());
            }
        }

        //apply descend css classes recorded on parent.
        if (parent.axisDescendsCssClasses != null) {
            for(String descendCssClass: parent.axisDescendsCssClasses) {            
                child.addStyleName(descendCssClass);
            }
        }
        
        if (child instanceof AxisPanel2) {
            AxisPanel2 childAxisPanel = (AxisPanel2)child;
            
            //apply layout descend styles recorded on parent.            
            if (parent.layoutDescendsStyles != null) {                
                for(Map.Entry<String,String> layoutDescendStyle: parent.layoutDescendsStyles.entrySet()) {            
                    childAxisPanel.layoutPanel.getElement().getStyle().setProperty(layoutDescendStyle.getKey(), layoutDescendStyle.getValue());
                }
            }
            
            //apply layout descend css classes recorded on parent.            
            if (parent.layoutDescendsCssClasses != null) {
                for(String layoutDescendCssClass: parent.layoutDescendsCssClasses) {            
                    childAxisPanel.layoutPanel.addStyleName(layoutDescendCssClass);
                }
            } 
            
            //repeat for all grand children.
            Iterator<Widget> grandChildIter = childAxisPanel.layoutPanel.iterator();
            while (grandChildIter.hasNext()) {
                Widget grandChild = grandChildIter.next();
                if (grandChild instanceof AxisWrapperPanel) {
                    applyDescendsStyles((AxisWrapperPanel)grandChild, childAxisPanel);
                }
            }
            
        } 
        
        applyDescendsStyles(child, getTargetableParentOf(parent));
    }
    
    /**
     * Clears all the child and descendent styles recorded for the specified panel.
     * author Sony Mathew
     */
    private void clearChildStyles(AxisPanel2 p) {
        p.axisChildsStyles = null;
        p.axisDescendsStyles = null;       
        
        p.axisChildsCssClasses = null;
        p.axisDescendsCssClasses = null;

        p.layoutChildsStyles = null;
        p.layoutDescendsStyles = null;
        
        p.layoutChildsCssClasses = null;
        p.layoutDescendsCssClasses = null;        
    }
    
    /**
     * Style the axis tree i.e. the target AxisPanel, all its descendent X/Y AxisPanels, 
     *  all widgets wrapped with AxisWrapperPanel. Will NOT style the Layouts and their descendents.
     *   
     * author Sony Mathew
     */
    public AxisPanel2 styleTree(String styleProp, String styleValue) {
        style(styleProp, styleValue);
        styleDescends(styleProp, styleValue);
        return this;
    }
    
    /**
     * Style the Layouts and their descendents starting with the target panel's Layout.
     *   
     * author Sony Mathew
     */
    public AxisPanel2 styleLayoutTree(String styleProp, String styleValue) {
        styleLayout(styleProp, styleValue);
        styleLayoutDescends(styleProp, styleValue);
        return this;
    }
        
    /**
     * @see #style(String, String)
     * 
     * Convenience for creating a solid border with given width and color.
     */
    public AxisPanel2 border(int borderWidthPixels, String borderColor) {
        style("borderStyle", "solid");
        style("borderColor", borderColor);
        style("borderWidth", borderWidthPixels+"px");        
        return this;
    }
    
    /**
     * @see #style(String, String)
     * 
     * Convenience for creating a solid black border with specified width in pixels. 
     */
    public AxisPanel2 border(int widthPixels) {
        border(widthPixels, "black");
        return this;
    }
    
    /**
     * @see #style(String, String)
     * 
     * Set a width in pixels of the target panel.
     */
    public AxisPanel2 widthPx(int pixels) {
        style("width", pixels+"px");      
        return this;
    }
    
    /**
     * @see #style(String, String)
     * 
     * Set a height on the target panel in pixels.
     */
    public AxisPanel2 heightPx(int pixels) {
        style("height", pixels+"px");
        return this;
    }
    
    
    /**
     * @see #style(String, String)
     *      
     * Set width and height pixels of the target panel.
     * 
     * Note: This does NOT support negative values to indicate relative values to parent.
     */
    public AxisPanel2 sizePx(int pixelWidth, int pixelHeight) {
        widthPx(pixelWidth);
        heightPx(pixelHeight);
        return this;
    }
    
    /**
     * Set a width of the target panel as percent of parent.
     */
    public AxisPanel2 widthPct(double widthPercentOfParent) {
        style("width", widthPercentOfParent+"%");      
        return this;
    }
    
    /**
     * Set a css height on the target panel (e.g. 20% or 20px)
     */
    public AxisPanel2 heightPct(double heightPercentOfParent) {
        style("height", heightPercentOfParent+"%");
        return this;
    }
    
    /**
     * Set width and height percents of the target panel.
     */
    public AxisPanel2 sizePct(double widthPercentOfParent, double heightPercentOfParent) {
        widthPct(widthPercentOfParent);
        heightPct(heightPercentOfParent);
        return this;
    }

    /**
     * Stretch the width of all panels in the tree (axis & layout tree) starting at target panel to 100%.
     */
    public AxisPanel2 treeStretchWidth() {
        styleTree("width", "100%");
        styleLayoutTree("width", "100%");
        return this;
    }
    
    /**
     * Stretch the height of all panels in the tree (axis & layout tree) starting at target panel to 100%.
     */
    public AxisPanel2 treeStretchHeight() {
        styleTree("height", "100%");
        styleLayoutTree("height", "100%");
        return this;
    }
    
    /**
     * @see #style(String, String)
     * 
     * Pad the target panel on all 4 sides with num pixels specified.
     */
    public AxisPanel2 padPx(int pixels) {
        style("padding", pixels+"px");
        return this;
    }

    /**
     * @see #style(String, String)
     * 
     * Pad the tree (axis tree only) starting at target panel on all 4 sides with num pixels specified.
     */
    public AxisPanel2 treePadPx(int pixels) {
        styleTree("padding", pixels+"px");
        return this;
    }

    /**
     * Align the target within its parent in the X (horizontal direction) or Y (Vertical Direction).
     * 
     * @param alignX: -1, 0, 1 for horizontal left, center, right
     * @param alignY:  1, 0, -1 for vertical top, middle, bottom.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 align(int alignX, int alignY) {
        Widget alignWidget = getTarget();
        Widget parentWidget = alignWidget.getParent();
        while(parentWidget != null && !(parentWidget instanceof XLayoutPanel) && !(parentWidget instanceof YLayoutPanel)) {
            alignWidget = parentWidget;
            parentWidget = alignWidget.getParent();
        }        

        if (parentWidget != null) {
            CellPanel parentLayoutPanel = (CellPanel)parentWidget;
            parentLayoutPanel.setCellHorizontalAlignment(alignWidget, fromAlignX(alignX));
            parentLayoutPanel.setCellVerticalAlignment(alignWidget, fromAlignY(alignX));
        }
        
        return this;
    }
    
    /**
     * Align all children of target in the X (horizontal direction) or Y (Vertical Direction).
     *  Must be done before children are added.
     * 
     * @param alignX: -1, 0, 1 for horizontal left, center, right
     * @param alignY:  1, 0, -1 for vertical top, middle, bottom.
     * 
     * author Sony Mathew
     */
    public AxisPanel2 alignChilds(int alignX, int alignY) {
        setAlign(getTarget().layoutPanel, alignX, alignY);
        return this;
    }
    
    /**
     * Provides the Horizontal alignment constant for the given X align.
     * 
     * author Sony Mathew
     */
    public static HorizontalAlignmentConstant fromAlignX(int xAlign) {
        switch(xAlign) {
        case -1:
            return HasHorizontalAlignment.ALIGN_LEFT;
        case 0:
            return HasHorizontalAlignment.ALIGN_CENTER;
        case 1:
            return HasHorizontalAlignment.ALIGN_RIGHT;
        default:
            throw new IllegalStateException("Bad X Align value provided [" + xAlign + "]");
        }        
    }
    
    /**
     * Provides the Vertical alignment constant for the given Y align.
     * 
     * author Sony Mathew
     */
    public static VerticalAlignmentConstant fromAlignY(int yAlign) {
        switch(yAlign) {
        case 1:
            return HasVerticalAlignment.ALIGN_TOP;
        case 0:
            return HasVerticalAlignment.ALIGN_MIDDLE;
        case -1:
            return HasVerticalAlignment.ALIGN_BOTTOM;            
        default:
            throw new IllegalStateException("Bad Y Align value provided [" + yAlign + "]");
        }        
    }
    
    /**
     * Set the X & Y alignment on the specified panel.
     * 
     * @param alignX: -1, 0, 1 for horizontal left, center, right
     * @param alignY:  1, 0, -1 for vertical top, middle, bottom.
     * 
     * author Sony Mathew
     */
    private void setAlign(CellPanel panel, int alignX, int alignY) {
        if (panel == null) {
            return;            
        }
        
        if (panel.iterator().hasNext()) {
            throw new IllegalStateException("Cannot Align when children already exist");
        }
        
        HorizontalAlignmentConstant hAlign = fromAlignX(alignX);        
        VerticalAlignmentConstant vAlign = fromAlignY(alignY);        
                
        if (panel instanceof HorizontalPanel) {
            HorizontalPanel hlayout = (HorizontalPanel)panel;
            hlayout.setHorizontalAlignment(hAlign);
            hlayout.setVerticalAlignment(vAlign);
        }else if (panel instanceof VerticalPanel) {
            VerticalPanel vlayout = (VerticalPanel)panel;
            vlayout.setHorizontalAlignment(hAlign);
            vlayout.setVerticalAlignment(vAlign);
        }else{
            throw new IllegalStateException("Unexpected: cannot align [" + panel.getClass() + "]");
        }
    }
}
